/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "mir_string.h"
#include "../Include/m_metacontacts.h"
#include "ctrl.h"

namespace DB 
{

	namespace MetaContact
	{
		/**
		 *
		 *
		 **/
		INT SubCount( HANDLE hMetaContact )
		{
			INT result = CallService( MS_MC_GETNUMCONTACTS, ( WPARAM ) hMetaContact, 0 );
			return ( result == CALLSERVICE_NOTFOUND ) ? -1 : result;
		}
		
		/**
		 *
		 *
		 **/
		INT SubDefNum( HANDLE hMetaContact )
		{
			INT result = CallService( MS_MC_GETDEFAULTCONTACTNUM, ( WPARAM ) hMetaContact, 0 );
			return ( result == CALLSERVICE_NOTFOUND ) ? -1 : result;
		}

		/**
		 *
		 *
		 **/
		HANDLE Sub( HANDLE hMetaContact, INT idx )
		{
			if( idx != -1 )
			{
				INT result = CallService( MS_MC_GETSUBCONTACT, ( WPARAM ) hMetaContact, ( LPARAM ) idx );
				return ( result == CALLSERVICE_NOTFOUND ) ? NULL : ( HANDLE ) result;
			}
			return NULL;
		}

		/**
		 *
		 *
		 **/
		BOOLEAN IsSub( HANDLE hContact )
		{
			return myGlobals.szMetaProto && 
						 DB::Setting::GetByte( myGlobals.szMetaProto, "Enabled", TRUE ) && 
						 DB::Setting::GetByte( hContact, myGlobals.szMetaProto, "IsSubcontact", FALSE );
		}

		/**
		 *
		 *
		 **/
		HANDLE GetMeta( HANDLE hContact )
		{
			INT result;

			if( myGlobals.szMetaProto )
			{
				result = CallService( MS_MC_GETMETACONTACT, ( WPARAM ) hContact, 0 );
				if( result == CALLSERVICE_NOTFOUND )
				{
					result = NULL;
				}
			}
			else
			{
				result = NULL;
			}
			return ( HANDLE ) result;
		}


	} /* MetaContact */

	/**
	 * This namespace contains all functions used to access or modify contacts in the database.
	 **/
	namespace Contact
	{
		/**
		 * This function retrieves the display name for a contact.
		 *
		 * @param		hContact   - handle to the contact
		 *
		 * @return	Returns the display name of a contact.
		 **/
		LPTSTR DisplayName( HANDLE hContact )
		{
			return ( LPTSTR ) CallService( MS_CLIST_GETCONTACTDISPLAYNAME, ( WPARAM ) hContact, GCDNF_TCHAR );
		}

		/**
		 * This function is used to retrieve a contact's basic protocol
		 *
		 * @param		hContact   - handle to the contact
		 *
		 * @return	This function returns the basic protocol of a contact.
		 **/
		LPSTR Proto( HANDLE hContact )
		{
			if( hContact )
			{
				INT result;

				result = CallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM ) hContact, NULL );
				return ( LPSTR ) ( ( result == CALLSERVICE_NOTFOUND ) ? NULL : result );
			}
			return NULL;
		}

/*
		LPTSTR ProtoName( HANDLE hContact )
		{
			LPSTR p = Proto( hContact );
			return mir_a2t( p );
		}
*/
		/**
		 *
		 *
		 **/
		INT GetCount()
		{
			return CallService( MS_DB_CONTACT_GETCOUNT, 0, 0 );
		}

		/**
		 * This function searches the first contact in the database and returns its handle.
		 *
		 * @retval	HANDLE	- handle of the next contact in the database	
		 * @retval	NULL		- no more contacts in the database
		 **/
		HANDLE FindFirst()
		{
			return ( HANDLE ) CallService( MS_DB_CONTACT_FINDFIRST, 0, 0 );
		}

		/**
		 * This function searches the next contact in the database and returns its handle.
		 *
		 * @param		hContact   - handle to the contact
		 *
		 * @retval	HANDLE	- handle of the next contact in the database	
		 * @retval	NULL		- no more contacts in the database
		 **/
		HANDLE FindNext( HANDLE hContact )
		{
			return ( HANDLE ) CallService( MS_DB_CONTACT_FINDNEXT, ( WPARAM ) hContact, 0 );
		}

		/**
		 * Simply adds a new contact without setting up any protocol or something else
		 *
		 * @return The function returns the HANDLE of the new contact
		 **/
		HANDLE Add()
		{
			return ( HANDLE ) CallService( MS_DB_CONTACT_ADD, 0, 0 );
		}

		/**
		 * This function deletes a contact from the database.
		 *
		 * @param		hContact   - handle to the contact
		 **/
		INT Delete( HANDLE hContact )
		{
			return CallService( MS_DB_CONTACT_DELETE, ( WPARAM ) hContact, 0 );
		}


		/**
		 * This function trys to guess, when an ICQ contact was added to database.
		 **/
		DWORD WhenAdded( DWORD dwUIN, LPCSTR szBaseProto )
		{
			DBEVENTINFO	dbei; 
			HANDLE			edbe;
			DWORD				dwEvtUIN;

			ZeroMemory( &dbei, sizeof( dbei ) );
			dbei.cbSize = sizeof( dbei );

			for( edbe = DB::Event::FindFirst( NULL ); edbe != NULL; edbe = DB::Event::FindNext( edbe ) )
			{
				// get eventtype and compare
				if( !DB::Event::GetInfo( edbe, &dbei ) && dbei.eventType == EVENTTYPE_ADDED )
				{
					if( !DB::Event::GetInfoWithData( edbe, &dbei ) )
					{
						// extract UIN and compare with given one
						memcpy( &dwEvtUIN, dbei.pBlob, sizeof( DWORD ) );
						MIR_FREE( dbei.pBlob );
						if( dwEvtUIN == dwUIN ) 
						{
							return dbei.timestamp;
						}
					}
				}
			}
			return 0;
		}

	} /* Contact */

	namespace Module
	{
		/**
		 * Deletes all settings in the module.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 *
		 * return:	nothing
		 **/
		VOID Delete( HANDLE hContact, LPCSTR szModule )
		{
			CEnumList	Settings;

			if( !Settings.EnumSettings( hContact, szModule ) )
			{
				INT i;

				for( i = 0; i < Settings.getCount(); i++ )
				{
					DB::Setting::Delete( hContact, szModule, Settings[i] );
				}
			}
		}

		static INT CheckIfModuleIsEmptyProc( LPCSTR szSetting, LPARAM lParam )
		{
			return 1;
		}

		/**
		 * This function tests, whether a module is empty for the given contact or not
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 *
		 * @retval	TRUE	- the module is empty
		 * @retval	FALSE	- the module contains settings
		 **/
		BOOLEAN IsEmpty( HANDLE hContact, LPCSTR szModule )
		{
			DBCONTACTENUMSETTINGS dbces;
			dbces.pfnEnumProc = CheckIfModuleIsEmptyProc;
			dbces.szModule = szModule;
			dbces.ofsSettings = 0;
			dbces.lParam = 0;
			return ( 0 > CallService( MS_DB_CONTACT_ENUMSETTINGS, ( WPARAM )hContact, ( LPARAM )&dbces ) );
		}

		/**
		 * This function tests, whether a module is a metacontact protocol
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 *
		 * @retval	TRUE	- the module is empty
		 * @retval	FALSE	- the module contains settings
		 **/
		BOOLEAN IsMeta( LPCSTR szModule )
		{
			return !mir_strcmp( szModule, myGlobals.szMetaProto );
		}

		/**
		 * This function tests, whether a module is a meta contact, and user wants to scan it for settings
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 *
		 * @retval	TRUE	- the module is empty
		 * @retval	FALSE	- the module contains settings
		 **/
		BOOLEAN IsMetaAndScan	( LPCSTR szModule )
		{
			return DB::Setting::GetByte( SET_META_SCAN, TRUE ) && IsMeta( szModule );
		}

	} /* namespace Module */

	namespace Setting
	{
		/**
		 * This function calls MS_DB_CONTACT_GETSETTING_STR service to get database values. 
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to read
		 * @param		destType			 - desired string type ( DBVT_ASCIIZ, DBVT_WCHAR, DBVT_UTF8 )
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT Get( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv, const BYTE destType )
		{
			INT result;
			DBCONTACTGETSETTING dgs;

			dgs.szModule = szModule;
			dgs.szSetting = szSetting;
			dgs.pValue = dbv;
			dbv->type = 0;
			
			// read value without translation to specific type
			result = CallService( MS_DB_CONTACT_GETSETTING_STR, ( WPARAM ) hContact, ( LPARAM ) &dgs );

			// Is value read successfully and destination type set?
			if( !result && destType )
			{
				result = DB::Variant::ConvertString( dbv, destType );
			}
			return result;
		}

		/**
		 * This function calls MS_DB_CONTACT_GETSETTING_STR service to get database values. 
		 * It searches in szModule first and if the setting does not exist there it tries proto to retrieve it.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szProto	   - the contact's protocol to read the setting from ( e.g. ICQ )
		 * @param		szSetting  - the setting to read
		 * @param		destType			 - desired string type ( DBVT_ASCIIZ, DBVT_WCHAR, DBVT_UTF8 )
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT GetEx( HANDLE hContact, LPCSTR szModule, LPCSTR szProto, LPCSTR szSetting, DBVARIANT *dbv, const BYTE destType )
		{
			INT result;

			result = !szModule || Get( hContact, szModule, szSetting, dbv, destType );
			
			// try to read setting from the contact's protocol module 
			if( result && szProto )
			{
				result = Get( hContact, szProto, szSetting, dbv, destType );

				// try to get setting from a metasubcontact
				if( result && DB::Module::IsMetaAndScan( szProto ) )
				{
					const INT def = DB::MetaContact::SubDefNum( hContact );
					HANDLE hSubContact;
		
					// try to get setting from the default subcontact first
					if( def > -1 && def < INT_MAX ) 
					{
						hSubContact = DB::MetaContact::Sub( hContact, def );
						if( hSubContact != NULL ) 
						{
							result = DB::Setting::GetEx( hSubContact, szModule, DB::Contact::Proto( hSubContact ), szSetting, dbv, destType );
						}
					}
					
					// scan all subcontacts for the setting
					if( result )
					{
						const INT cnt = DB::MetaContact::SubCount( hContact );

						if( cnt < INT_MAX ) 
						{
							INT i;

							for( i = 0; result && i < cnt; i++ ) 
							{
								if( i != def )
								{
									hSubContact = DB::MetaContact::Sub( hContact, i );
									if( hSubContact != NULL ) 
									{
										result = DB::Setting::GetEx( hSubContact, szModule, DB::Contact::Proto( hSubContact ), szSetting, dbv, destType );
			}	}	}	}	}	}	}
			return result;
		}

		/**
		 * This function is used by the controls of the details dialog and calls MS_DB_CONTACT_GETSETTING_STR service
		 * to get database values. It searches in szModule first and if the setting does not exist there it tries proto
		 * to retrieve it.
		 *
		 * @param		hContact		- handle to the contact
		 * @param		szModule		- the module to read the setting from ( e.g. USERINFO )
		 * @param		szSubModule	- the module to read the setting from a meta subcontract ( e.g. USERINFO )
		 * @param		szProto			- the contact's protocol to read the setting from ( e.g. ICQ )
		 * @param		szSetting		- the setting to read
		 * @param		destType				- desired string type ( DBVT_ASCIIZ, DBVT_WCHAR, DBVT_UTF8 )
		 *
		 * @return	This function returns the WORD which contains the source of information.
		 **/
		WORD GetCtrl( HANDLE hContact, LPCSTR szModule, LPCSTR szSubModule, LPCSTR szProto, LPCSTR szSetting, DBVARIANT *dbv, const BYTE destType )
		{
			WORD wFlags = 0;

			// read setting from given module
			if( hContact && szModule && *szModule && !Get( hContact, szModule, szSetting, dbv, destType ) ) 
			{
				wFlags |= CTRLF_HASCUSTOM;
				if( Exists( hContact, szProto, szSetting ) )
				{
					wFlags |= CTRLF_HASPROTO;
				}
			}

			// read setting from contact's basic protocol
			else if( szProto && *szProto ) 
			{	
				// try to read the setting from the basic protocol
				if( !Get( hContact, szProto, szSetting, dbv, destType ) )
				{
					wFlags |= CTRLF_HASPROTO;
				}
				
				// try to read the setting from the sub contacts' modules
				else if( DB::Module::IsMetaAndScan( szProto ) ) 
				{
					const INT def = DB::MetaContact::SubDefNum( hContact );
					HANDLE hSubContact;
		
					// try to get setting from the default subcontact first
					if( def > -1 && def < INT_MAX ) 
					{
						hSubContact = DB::MetaContact::Sub( hContact, def );
						if( hSubContact != NULL ) 
						{
							wFlags = GetCtrl( hSubContact, szSubModule, NULL, DB::Contact::Proto( hSubContact ), szSetting, dbv, destType );
							if( wFlags != 0 )
							{
								wFlags &= ~CTRLF_HASCUSTOM;
								wFlags |= CTRLF_HASMETA;
							}
						}
					}

					// copy the missing settings from the other subcontacts
					if( wFlags == 0 )
					{
						const INT cnt = DB::MetaContact::SubCount( hContact );
						INT i;

						for( i = 0; i < cnt; i++ ) 
						{
							if( i != def )
							{
								hSubContact = DB::MetaContact::Sub( hContact, i );
								if( hSubContact != NULL ) 
								{
									wFlags = GetCtrl( hSubContact, szSubModule, NULL, DB::Contact::Proto( hSubContact ), szSetting, dbv, destType );
									if( wFlags != 0 )
									{
										wFlags &= ~CTRLF_HASCUSTOM;
										wFlags |= CTRLF_HASMETA;
										break;
			}	}	}	}	}	}	}
			if( wFlags == 0 )
			{
				dbv->type = DBVT_DELETED;
			}
			return wFlags;
		}

		/**
		 * This function reads a setting from database into a predefined portion of memory
		 * and convert numbers into a string, too.
		 *
		 * @param		hContact		- handle to the contact
		 * @param		szModule		- the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting		- the setting to read
		 * @param		szValue			- buffer, that retrieves the value
		 * @param		cchValue		- number of characters the buffer can take
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT GetStatic( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, LPSTR szValue, INT cchValue )
		{
			DBVARIANT dbv;
			DBCONTACTGETSETTING sVal;

			if( szValue && cchValue )
			{
				szValue[0] = 0;

				dbv.pszVal = szValue;
				dbv.cchVal = cchValue;
				dbv.type = DBVT_ASCIIZ;
				
				sVal.pValue = &dbv;
				sVal.szModule = szModule;
				sVal.szSetting = szSetting;
				
				if( !CallService( MS_DB_CONTACT_GETSETTINGSTATIC, ( WPARAM )hContact, ( LPARAM )&sVal ) ) 
				{
					switch( dbv.type ) 
					{
						case DBVT_BYTE:
							{
								_itoa( dbv.bVal, szValue, 10 );
							}
							break;
						case DBVT_WORD:
							{
								_itoa( dbv.wVal, szValue, 10 );
							}
							break;
						case DBVT_DWORD:
							{
								_itoa( dbv.dVal, szValue, 10 );
							}
							break;
					}
					return ( szValue[0] == 0 );
				}
			}
			return 1;
		}

		/**
		 * This function reads a byte from the database. If required it converts it to a byte value
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to read
		 * @param		errorValue - value to return if something goes wrong
		 *
		 * @return	byte value
		 **/
		BYTE GetByte( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, BYTE errorValue )
		{
			DBVARIANT dbv;
			BYTE result;

			if( GetAsIs( hContact, szModule, szSetting, &dbv ) )
			{
				result = errorValue;
			}
			else
			{
				switch( dbv.type ) 
				{
					case DBVT_BYTE:
						{
							result = dbv.bVal;
							break;
						}
					case DBVT_WORD:
						{
							result = ( dbv.wVal < 0x0100 ) ? ( BYTE ) dbv.wVal : errorValue;
							break;
						}
					case DBVT_DWORD:
						{
							result = ( dbv.wVal < 0x00000100 ) ? ( BYTE ) dbv.dVal : errorValue;
							break;
						}
					default:
						{
							DB::Variant::Free( &dbv );
							result = errorValue;
						}
				}
			}
			return result;
		}		

		/**
		 * This function reads a word from the database. If required it converts it to a word value
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to read
		 * @param		errorValue - value to return if something goes wrong
		 *
		 * @return	word value
		 **/
		WORD GetWord( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, WORD errorValue )
		{
			DBVARIANT dbv;
			WORD result;

			if( GetAsIs( hContact, szModule, szSetting, &dbv ) )
			{
				result = errorValue;
			}
			else
			{
				switch( dbv.type ) 
				{
					case DBVT_BYTE:
						{
							result = 0x00ff & dbv.bVal;
							break;
						}
					case DBVT_WORD:
						{
							result = dbv.wVal;
							break;
						}
					case DBVT_DWORD:
						{
							result = ( dbv.wVal < 0x00010000 ) ? ( WORD ) dbv.dVal : errorValue;
							break;
						}
					default:
						{
							DB::Variant::Free( &dbv );
							result = errorValue;
						}
				}
			}
			return result;
		}	

		/**
		 * This function reads a double word from the database. If required it converts it to a double word value
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to read
		 * @param		errorValue - value to return if something goes wrong
		 *
		 * @return	double word value
		 **/
		DWORD GetDWord( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, DWORD errorValue )
		{
			DBVARIANT dbv;
			DWORD result;

			if( GetAsIs( hContact, szModule, szSetting, &dbv ) )
			{
				result = errorValue;
			}
			else
			{
				switch( dbv.type ) 
				{
					case DBVT_BYTE:
						{
							result = 0x000000ff & dbv.bVal;
							break;
						}
					case DBVT_WORD:
						{
							result = 0x0000ffff & dbv.wVal;
							break;
						}
					case DBVT_DWORD:
						{
							result = dbv.dVal;
							break;
						}
					default:
						{
							DB::Variant::Free( &dbv );
							result = errorValue;
						}
				}
			}
			return result;
		}

		/**
		 * This function reads a value from the database and returns it as an ansi encoded string. 
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to read
		 *
		 * @return	string value
		 **/
		LPSTR GetAString( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting )
		{
			DBVARIANT dbv;

			if( GetAString( hContact, szModule, szSetting, &dbv ) == 0)
			{
				if( DB::Variant::ToString( &dbv, DBVT_WCHAR ) == 0)
				{
					return dbv.pszVal;
				}
				DB::Variant::Free( &dbv );
			}
			return NULL;
		}

		/**
		 * This function reads a value from the database and returns it as an unicode encoded string. 
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to read
		 *
		 * @return	string value
		 **/
		LPWSTR GetWString( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting )
		{
			DBVARIANT dbv;

			if( GetWString( hContact, szModule, szSetting, &dbv ) == 0)
			{
				if( DB::Variant::ToString( &dbv, DBVT_WCHAR ) == 0)
				{
					return dbv.pwszVal;
				}
				DB::Variant::Free( &dbv );
			}
			return NULL;
		}

		/**
		 * This function calls MS_DB_CONTACT_WRITESETTING to write a DBVARIANT structure to the database.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to write
		 * @param		dbv				 - the DBVARIANT to store
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT WriteVariant( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, const DBVARIANT *dbv )
		{
			DBCONTACTWRITESETTING cws;

			cws.szModule = szModule;
			cws.szSetting = szSetting;
			memcpy( &cws.value, dbv, sizeof( DBVARIANT ) );
			return CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM ) hContact, ( LPARAM ) &cws );
		}

		/**
		 * This function calls MS_DB_CONTACT_WRITESETTING to write a BYTE to the database.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to write
		 * @param		value			 - the byte to store
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT WriteByte( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, BYTE value )
		{
			DBCONTACTWRITESETTING cws;

			cws.szModule = szModule;
			cws.szSetting = szSetting;
			cws.value.type = DBVT_BYTE;
			cws.value.bVal = value;
			return CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM ) hContact, ( LPARAM ) &cws );
		}

		/**
		 * This function calls MS_DB_CONTACT_WRITESETTING to write a WORD to the database.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to write
		 * @param		value			 - the word to store
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT WriteWord( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, WORD value )
		{
			DBCONTACTWRITESETTING cws;

			cws.szModule = szModule;
			cws.szSetting = szSetting;
			cws.value.type = DBVT_WORD;
			cws.value.wVal = value;
			return CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM ) hContact, ( LPARAM ) &cws );
		}

		/**
		 * This function calls MS_DB_CONTACT_WRITESETTING to write a DWORD to the database.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to write
		 * @param		value			 - the double word to store
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT WriteDWord( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, DWORD value )
		{
			DBCONTACTWRITESETTING cws;

			cws.szModule = szModule;
			cws.szSetting = szSetting;
			cws.value.type = DBVT_DWORD;
			cws.value.dVal = value;
			return CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM ) hContact, ( LPARAM ) &cws );
		}

		/**
		 * This function calls MS_DB_CONTACT_WRITESETTING to write an ansi string to the database.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to write
		 * @param		value			 - the string to store
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT WriteAString( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, LPSTR value )
		{
			DBCONTACTWRITESETTING cws;

			cws.szModule = szModule;
			cws.szSetting = szSetting;
			cws.value.type = DBVT_ASCIIZ;
			cws.value.pszVal = value;
			return CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM ) hContact, ( LPARAM ) &cws );
		}

		/**
		 * This function calls MS_DB_CONTACT_WRITESETTING to write an unicode string to the database.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to write
		 * @param		value			 - the string to store
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT WriteWString( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, LPWSTR value )
		{
			DBCONTACTWRITESETTING cws;

			cws.szModule = szModule;
			cws.szSetting = szSetting;
			cws.value.type = DBVT_WCHAR;
			cws.value.pwszVal = value;
			return CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM ) hContact, ( LPARAM ) &cws );
		}

		/**
		 * This function calls MS_DB_CONTACT_WRITESETTING to write an utf8 string to the database.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to write
		 * @param		value			 - the string to store
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT WriteUString( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, LPSTR value )
		{
			DBCONTACTWRITESETTING cws;

			cws.szModule = szModule;
			cws.szSetting = szSetting;
			cws.value.type = DBVT_UTF8;
			cws.value.pszVal = value;
			return CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM ) hContact, ( LPARAM ) &cws );
		}

		/**
		 * This function checks for the existence of the given setting in the database
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to check
		 *
		 * @retval	TRUE	- setting exists
		 * @retval	FALSE	- setting does not exist
		 **/
		BOOLEAN Exists( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting )
		{
			if( szModule && szSetting ) 
			{
				DBCONTACTGETSETTING cgs;
				DBVARIANT dbv;
				CHAR szDummy[1];

				dbv.pszVal = szDummy;
				dbv.cchVal = sizeof( szDummy );
				dbv.type = 0;
				cgs.pValue = &dbv;
				cgs.szModule = szModule;
				cgs.szSetting = szSetting;
				if( !CallService( MS_DB_CONTACT_GETSETTINGSTATIC, ( WPARAM ) hContact, ( LPARAM ) &cgs ) )
				{
					return ( dbv.type > DBVT_DELETED );
				}
			}
			return FALSE;
		}

		/**
		 * This function deletes the given setting from database 
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		szSetting  - the setting to read
		 *
		 * @retval	0	- success
		 * @retval	1 - failure
		 **/
		INT Delete( HANDLE hContact, LPCSTR szModule, LPCSTR szSetting )
		{
			DBCONTACTGETSETTING cgs;
			cgs.szModule = szModule;
			cgs.szSetting = szSetting;
			return CallService( MS_DB_CONTACT_DELETESETTING, ( WPARAM ) hContact, ( LPARAM ) &cgs );
		}

		/**
		 * This function deletes all reluctant settings of an setting array such as My-phoneXX.
		 *
		 * @param		hContact   - handle to the contact
		 * @param		szModule   - the module to read the setting from ( e.g. USERINFO )
		 * @param		pszFormat	- the format, telling what a array of settings is ment
		 * @param		iStart		- the first index of the setting to delete
		 *
		 * @return	nothing
		 **/
		VOID DeleteArray( HANDLE hContact, LPCSTR szModule, LPCSTR pszFormat, INT iStart )
		{
			CHAR szSetting[MAXSETTING];
			do 
			{
				mir_snprintfA( szSetting, MAXSETTING, pszFormat, iStart++ );
			} 
			while( !DB::Setting::Delete( hContact, szModule, szSetting ) );
		}

		/**
		 * This function can prevent a setting from being stored to database permanently.
		 *
		 * @param		szSetting  - the setting to read
		 * @param		enabled		 - if set to 'true' the setting will not be stored in database
		 *
		 * @retval	0	- success
		 * @retval	1 - failure
		 **/
		INT Resident( LPCSTR szSetting, const bool enabled )
		{
			return CallService( MS_DB_SETSETTINGRESIDENT, ( WPARAM ) enabled, ( LPARAM ) szSetting );
		}

	} /* namespace Setting */

	namespace Variant
	{
		INT Free( DBVARIANT *dbv )
		{
			return CallService( MS_DB_CONTACT_FREEVARIANT, 0, ( LPARAM ) dbv );
		}

		/**
		 * This function completely converts a DBVARIANT to the destination string type.
		 * It includes BYTE, WORD, DWORD and all string types
		 *
		 * @param		dbv				- pointer to DBVARIANT structure which is to manipulate
		 * @param		destType	- one of ( DBVT_ASCIIZ, DBVT_UTF8 or DBVT_WCHAR )
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT ToString( DBVARIANT* dbv, const BYTE destType )
		{
			if( dbv )
			{
				switch( destType ) 
				{
					// destination type is "utf8" or "ascii"
					case DBVT_ASCIIZ:
					case DBVT_UTF8:
						{
							CHAR buf[32];

							switch( dbv->type ) 
							{
								// source value is of type "byte"
								case DBVT_BYTE:
									{
										_ultoa( dbv->bVal, buf, 10 );
										dbv->pszVal = mir_strdup( buf );
										dbv->type = ( dbv->pszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// source value is of type "word"
								case DBVT_WORD:
									{
										_ultoa( dbv->wVal, buf, 10 );
										dbv->pszVal = mir_strdup( buf );
										dbv->type = ( dbv->pszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// source value is of type "dword"
								case DBVT_DWORD:
									{
										_ultoa( dbv->dVal, buf, 10 );
										dbv->pszVal = mir_strdup( buf );
										dbv->type = ( dbv->pszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// source value is of any string type
								case DBVT_ASCIIZ:
								case DBVT_WCHAR:
								case DBVT_UTF8:
									{
										return ConvertString( dbv, destType );
									}
							}
						}
						break;

					// destination type is "wchar"
					case DBVT_WCHAR:
						{
							WCHAR buf[32];

							switch( dbv->type ) 
							{
								// source value is of type "byte"
								case DBVT_BYTE:
									{
										_ultow( dbv->bVal, buf, 10 );
										dbv->pwszVal = mir_wcsdup( buf );
										dbv->type = ( dbv->pwszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// source value is of type "word"
								case DBVT_WORD:
									{
										_ultow( dbv->wVal, buf, 10 );
										dbv->pwszVal = mir_wcsdup( buf );
										dbv->type = ( dbv->pwszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// source value is of type "dword"
								case DBVT_DWORD:
									{
										_ultow( dbv->dVal, buf, 10 );
										dbv->pwszVal = mir_wcsdup( buf );
										dbv->type = ( dbv->pwszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// source value is of any string type
								case DBVT_ASCIIZ:
								case DBVT_WCHAR:
								case DBVT_UTF8:
									{
										return ConvertString( dbv, destType );
									}
							}
						}
				}
				return dbv->type != destType;
			}
			return 1;
		}

		/**
		 * This function converts a string value of the DBVARIANT to the destination type
		 * but keeps all other values as is.
		 *
		 * @param		dbv				- pointer to DBVARIANT structure which is to manipulate
		 * @param		destType	- one of ( DBVT_ASCIIZ, DBVT_UTF8 or DBVT_WCHAR )
		 *
		 * @retval	0 - success
		 * @retval	1 - error
		 **/
		INT ConvertString( DBVARIANT* dbv, const BYTE destType )
		{
			if( dbv )
			{
				switch( dbv->type ) 
				{
					// source value is of type "ascii"
					case DBVT_ASCIIZ:
						{
							switch( destType ) 
							{
								// destination type is "utf8"
								case DBVT_UTF8:
									{
										LPSTR tmpBuf = mir_utf8encode( dbv->pszVal );
									
										mir_free( dbv->pszVal );
										dbv->pszVal = tmpBuf;
										dbv->type = ( dbv->pszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// destination type is "wchar"
								case DBVT_WCHAR:
									{
										LPWSTR tmpBuf = mir_a2u( dbv->pszVal );
											
										mir_free( dbv->pszVal );
										dbv->pwszVal = tmpBuf;
										dbv->type = ( dbv->pwszVal ) ? destType : DBVT_DELETED;
									}
							}
						}
						break;
					
					// source value is of type "utf8"
					case DBVT_UTF8:
						{
							switch( destType ) 
							{
								// destination type is "ascii"
								case DBVT_ASCIIZ:
									{
										mir_utf8decode( dbv->pszVal, NULL );
										dbv->type = ( dbv->pszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// destination type is "wchar"
								case DBVT_WCHAR:
									{
										LPSTR savePtr = dbv->pszVal;

										dbv->pszVal = NULL;
										mir_utf8decode( savePtr, &dbv->pwszVal );
										mir_free( savePtr );
										dbv->type = ( dbv->pwszVal ) ? destType : DBVT_DELETED;
									}
							}
						}
						break;

					// source value is of type "wchar"
					case DBVT_WCHAR:
						{
							switch( destType ) 
							{
								// destination type is "ascii"
								case DBVT_ASCIIZ:
									{
										LPSTR tmpBuf = mir_u2a( dbv->pwszVal );
											
										mir_free( dbv->pwszVal );
										dbv->pszVal = tmpBuf;
										dbv->type = ( dbv->pszVal ) ? destType : DBVT_DELETED;
									}
									break;

								// destination type is "utf8"
								case DBVT_UTF8:
									{
										LPSTR tmpBuf = mir_utf8encodeW( dbv->pwszVal );
									
										mir_free( dbv->pwszVal );
										dbv->pszVal = tmpBuf;
										dbv->type = ( dbv->pszVal ) ? destType : DBVT_DELETED;
									}
							}
						}
				}
				return dbv->type == DBVT_DELETED;
			}
			return 1;
		}

	} /* namespace Variant */

	namespace Event
	{
		/**
		 * This function searches for the first event for the given contact.
		 *
		 * @param	hContact	- the handle of the contact to search events for
		 *
		 * @return This function returns the HANDLE of the first event for the given contact.
		 **/
		HANDLE FindFirst( HANDLE hContact )
		{
			return ( HANDLE )CallService( MS_DB_EVENT_FINDFIRST, ( WPARAM )hContact, 0 );
		}

		/**
		 * This function searches for the last event for the given contact.
		 *
		 * @param	hContact	- the handle of the contact to search events for
		 *
		 * @return This function returns the HANDLE of the last event for the given contact.
		 **/
		HANDLE FindLast( HANDLE hContact )
		{
			return ( HANDLE )CallService( MS_DB_EVENT_FINDLAST, ( WPARAM )hContact, 0 );
		}

		/**
		 * This function searches for the next event in the chain, which follows the given event.
		 *
		 * @param	hEvent	- the handle of the event where to continue searching
		 *
		 * @return This function returns the HANDLE of the next event in the event chain.
		 **/
		HANDLE FindNext( HANDLE hEvent )
		{
			return ( HANDLE )CallService( MS_DB_EVENT_FINDNEXT, ( WPARAM )hEvent, 0 );
		}

		/**
		 * This function searches for the previous event in the chain, which follows the given event.
		 *
		 * @param	hEvent	- the handle of the event where to continue searching
		 *
		 * @return This function returns the HANDLE of the previous event in the event chain.
		 **/
		HANDLE FindPrev( HANDLE hEvent )
		{
			return ( HANDLE )CallService( MS_DB_EVENT_FINDPREV, ( WPARAM )hEvent, 0 );
		}

		/**
		 * This function initializes the DBEVENTINFO structure and calls 
		 * the MS_DB_EVENT_GET service to retrieve information about an event.
		 *
		 * @param	hEvent	- the handle of the event to get information for
		 * @param	dbei		- the pointer to a DBEVENTINFO structure, which retrieves all information.
		 *
		 * @retval	0					- success
		 * @retval	nonezero	- failure
		 **/
		INT GetInfo( HANDLE hEvent, DBEVENTINFO *dbei )
		{
			dbei->cbSize = sizeof( DBEVENTINFO );
			dbei->cbBlob = 0;
			return CallService( MS_DB_EVENT_GET, ( WPARAM )hEvent, ( LPARAM )dbei );
		}

		/**
		 * This function initializes the DBEVENTINFO structure and calls 
		 * the MS_DB_EVENT_GET service to retrieve information about an event.
		 *
		 * @param	hEvent	- the handle of the event to get information for
		 * @param	dbei		- the pointer to a DBEVENTINFO structure, which retrieves all information.
		 *
		 * @retval	0					- success
		 * @retval	nonezero	- failure
		 **/
		INT GetInfoWithData( HANDLE hEvent, DBEVENTINFO *dbei )
		{
			INT result;

			dbei->cbSize = sizeof( DBEVENTINFO );
			dbei->cbBlob = BlobSizeOf( hEvent );

			if( dbei->cbBlob )
			{
				dbei->pBlob = ( PBYTE ) mir_alloc( dbei->cbBlob );
				if( dbei->pBlob == NULL )
				{
					dbei->cbBlob = 0;
				}
			}
			else
			{
				dbei->pBlob = NULL;
			}

			result = CallService( MS_DB_EVENT_GET, ( WPARAM )hEvent, ( LPARAM )dbei );
			if( result && dbei->pBlob )
			{
				mir_free( dbei->pBlob );
				dbei->pBlob = NULL;
			}
			return result;
		}

		/**
		 * This function returns the timestamp for the given event.
		 *
		 * @param	hEvent	- the handle of the event to get the timestamp for
		 *
		 * @retval	0 if no timestamp is available
		 * @retval	timestamp
		 **/
		DWORD TimeOf( HANDLE hEvent )
		{
			DBEVENTINFO dbei;

			if( !GetInfo( hEvent, &dbei ) )
			{
				return dbei.timestamp;
			}
			return 0;
		}
		
		/**
		 * This function returns the number of bytes required to retrieve
		 * binary data associated with the event.
		 *
		 * @param	hEvent	- the handle of the event to get the timestamp for
		 *
		 * @retval	size of event data
		 * @retval	-1 if hEvent is invalid
		 **/
		DWORD BlobSizeOf( HANDLE hEvent )
		{
			return CallService( MS_DB_EVENT_GETBLOBSIZE, ( WPARAM )hEvent, 0 );
		}

		/**
		 * This function compares two DBEVENTINFO structures against each other.
		 * It compares the timestamp, eventType and module names.
		 *
		 * @param	d1	- pointer to the first DBEVENTINFO structure
		 * @param	d2	- pointer to the second DBEVENTINFO structure
		 *
		 * @retval	TRUE	- The structures describe the same event.
		 * @retval	FALSE	- The two structure's events differ from each other.
		 **/
		static __inline BOOLEAN IsEqual( const DBEVENTINFO *d1, const DBEVENTINFO *d2 )
		{
			return d1 && d2 && 
				( d1->timestamp == d2->timestamp ) && 
				( d1->eventType == d2->eventType ) &&
				( !d1->szModule || !d2->szModule || !stricmp(d1->szModule, d2->szModule ) );
		}

		/**
		 * This functions searches for an existing event in the database, which matches
		 * the information provided by 'dbei'. In order to fasten up the search e.g. 
		 * while checking many events, this function stars searching from the last
		 * found event.
		 *
		 * @param		hContact					- the handle of the contact to search events for
		 * @param		hDbExistingEvent	- an existing database event to start the search from.
		 * @param		dbei							- the pointer to a DBEVENTINFO structure
		 *
		 * @retval	TRUE	- the event identified by its information in @c dbei exists.
		 * @retval	FALSE	- no event with the information of @c dbei exists.
		 *
		 **/
		BOOLEAN Exists( HANDLE hContact, HANDLE& hDbExistingEvent, DBEVENTINFO *dbei )
		{
			BOOLEAN result = FALSE;
			DBEVENTINFO edbei;
			HANDLE sdbe, edbe;

			if( !hDbExistingEvent )
			{
				hDbExistingEvent = FindFirst( hContact );
				if( hDbExistingEvent )
				{
					if( !GetInfo( hDbExistingEvent, &edbei ) )
					{
						if( ( dbei->timestamp < edbei.timestamp ) )
						{
							return FALSE;
						}
						if( IsEqual( dbei, &edbei ) )
						{
							return TRUE;
						}
					}
					edbe = FindLast( hContact );

					if( edbe == hDbExistingEvent )
					{
						return FALSE;
					}
					hDbExistingEvent = edbe;
				}
			}

			if( hDbExistingEvent )
			{
				sdbe = hDbExistingEvent;

				for(
						edbe = sdbe;
						edbe && !GetInfo( edbe, &edbei ) && ( dbei->timestamp <= edbei.timestamp );
						edbe = FindPrev( edbe )
					 )
				{
					hDbExistingEvent = edbe;

					result = IsEqual( dbei, &edbei );
					if( result ) 
					{
						break;
					}
				}

				if( !result )
				{
					for(
							edbe = FindNext( sdbe );
							edbe && !GetInfo( edbe, &edbei ) && ( dbei->timestamp >= edbei.timestamp );
							edbe = FindNext( edbe )
						 )
					{
						hDbExistingEvent = edbe;

						result = IsEqual( dbei, &edbei );
						if( result ) 
						{
							break;
						}
					}
				}
			}
			return result;
		}

	} /* namespace Events */


	INT CEnumList::EnumProc( LPCSTR pszName, DWORD ofsModuleName, LPARAM lParam )
	{
		if( pszName )
		{
			( ( CEnumList* )lParam )->Insert( pszName );
		}
		return 0;
	}
		
	INT CEnumList::EnumSettingsProc( LPCSTR pszName, LPARAM lParam )
	{
		return EnumProc( pszName, 0, lParam );
	}

	INT CEnumList::CompareProc( LPCSTR p1, LPCSTR p2 )
	{
		if( p1 )
		{
			if( p2 )
			{
				return strcmp( p1, p2 );
			}
			return 1;
		}
		return 0;
	}

	CEnumList::CEnumList()	: LIST<CHAR>( 50, (FTSortFunc)CEnumList::CompareProc )
	{
	}

	CEnumList::~CEnumList() 
	{ 
		INT i, cnt;
		LPSTR p;

		for( i = 0, cnt = getCount(); i < cnt; i++ )
		{
			p = (*this)[i]; 
			if( p )
			{
				mir_free( p );
			}
		}
		destroy();
	}

	LPSTR CEnumList::Insert( LPCSTR str )
	{
		LPSTR p = mir_strdup( str );
		if( p && !insert( p ) )
		{
			mir_free( p );
			p = NULL;
		}
		return p;
	}

	INT CEnumList::EnumModules()
	{
		return CallService( MS_DB_MODULES_ENUM, ( WPARAM )this, ( LPARAM )CEnumList::EnumProc );
	}

	INT CEnumList::EnumSettings( HANDLE hContact, LPCSTR szModule )
	{
		DBCONTACTENUMSETTINGS dbces;
		
		dbces.pfnEnumProc = (DBSETTINGENUMPROC)CEnumList::EnumSettingsProc;
		dbces.szModule = szModule;
		dbces.lParam = ( LPARAM )this;
		dbces.ofsSettings = 0;
		return CallService( MS_DB_CONTACT_ENUMSETTINGS, ( WPARAM )hContact, ( LPARAM )&dbces );
	}

} /* namespace DB */